package com.cx.common.core.utils.googleauthenticator;

import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;

import java.security.SecureRandom;

/**
 * @author _
 * @date 2020-08-24
 */
public class GoogleAuthenticator {

    /**
     * 时间前后偏移量
     */
    private static final int TIME_EXCURSION = 3;
    /**
     * 默认验证码长度
     */
    private static final int DEFAULT_LENGTH = 4;
    private static final char[] chars = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
            'Y', 'Z', '2', '3', '4', '5', '6', '7',
            '='
    };

    /**
     * 随机生成一个16位密钥
     */
    public static String createSecretKey16Bit() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[16];
        random.nextBytes(bytes);

        StringBuilder key = new StringBuilder();
        for (int i = 0; i < 16; i++) {
            char c = (char) bytes[i];
            key.append(chars[c & 31]);
        }

        return key.toString();
    }

    /**
     * 随机生成一个密钥32位
     */
    public static String createSecretKey() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[20];
        random.nextBytes(bytes);

        Base32 base32 = new Base32();
        String secretKey = base32.encodeToString(bytes);
        return secretKey.toUpperCase();
    }

    /**
     * 根据密钥获取验证码
     * 返回字符串是因为验证码有可能以 0 开头
     *
     * @param secretKey 密钥
     * @param time      第几个 30 秒 System.currentTimeMillis() / 1000 / 30
     */
    public static String getTotp(String secretKey, long time) {
        return getTotp(secretKey, time, DEFAULT_LENGTH);
    }

    /**
     * 根据密钥获取验证码
     * 返回字符串是因为验证码有可能以 0 开头
     *
     * @param secretKey 密钥
     * @param time      第几个 30 秒 System.currentTimeMillis() / 1000 / 30
     * @param len       验证码长度
     */
    public static String getTotp(String secretKey, long time, int len) {
        Base32 base32 = new Base32();
        byte[] bytes = base32.decode(secretKey.toUpperCase());
        String hexKey = Hex.encodeHexString(bytes);
        String hexTime = Long.toHexString(time);
        return Totp.generateTOTP(hexKey, hexTime, len);
    }

    /**
     * 校验方法带偏移量
     *
     * @param secretKey 密钥
     * @param code      用户输入的 TOTP 验证码
     */
    public static boolean verifyExcursion(String secretKey, String code) {
        return verifyExcursion(secretKey, code, DEFAULT_LENGTH, 30);
    }

    /**
     * 校验方法带偏移量
     *
     * @param secretKey 密钥
     * @param code      用户输入的 TOTP 验证码
     * @param len       验证码长度
     */
    public static boolean verifyExcursion(String secretKey, String code, int len) {
        return verifyExcursion(secretKey, code, len, 30);
    }

    /**
     * 校验方法带偏移量
     *
     * @param secretKey 密钥
     * @param code      用户输入的 TOTP 验证码
     * @param len       验证码长度
     * @param expire    有效时长秒
     */
    public static boolean verifyExcursion(String secretKey, String code, int len, long expire) {
        return verifyExcursion(secretKey, code, System.currentTimeMillis(), len, expire);
    }

    public static boolean verifyExcursion(String secretKey, String code, long currentTime, int len, long expire) {
        long time = currentTime / 1000 / expire;
        for (int i = -TIME_EXCURSION; i <= TIME_EXCURSION; i++) {
            String totp = getTotp(secretKey, time + i, len);
            if (totp.equals(code)) {
                return true;
            }
        }
        return false;
    }
}
